import exceptions
import traceback
import os
import os.path

import support

def _get_len_of_longest_task_name():
    # TODO: nasty code, but can we avoid the circular dependency?
    from _tasks import TaskManager as _TaskManager
    return _TaskManager.get_instance().len_of_longest_task_name()

def _get_local_user():
    # TODO: nasty code, but can we avoid the circular dependency?
    from _users import User as _User
    return _User("$")


class UnknownEventError(exceptions.Exception): pass


class ReceiptManager(support.Singleton):
    
    receipts = property(lambda self: self.__receipts)
    
    def __init__(self):
        self.__receipts = []
    
    def add_receipt(self, receipt):
        self.__receipts.append(receipt)
    
    def remove_receipt(self, receipt):
        self.__receipts.remove(receipt)
    
    def log_event(self, event):    
        
        # log the event to every receipt
        for receipt in self.__receipts:
            try:
                handler = getattr(receipt, "handle_%s" % event.__class__.__name__)
            except AttributeError:
                try:
                    handler = receipt.handle
                except AttributeError:
                    handler = None

            if handler:
                handler(event)
            else:
                raise UnknownEventError("'%s' does not know how to render event of type '%s'" % (receipt.__class__, event.__class__))


class TextualReceipt(object):
    """Standard console receipt renderer, renders everything to
    a given stream (likely sys.stdout)"""
    
    def __init__(self, stream):
        self.__stream = stream
        self.__cwd_abspath = os.path.abspath(os.getcwd()) + support.os_path_separator
        self.__last_fullname = None
        self.__last_context = None
        self.__last_details = None
    
    def handle_ErrorEvent(self, event):
        backtrace = "".join(traceback.format_tb(event.exception_info[2]))
        exc_name = str(event.exception_info[0])
        exc_name = exc_name.replace("exceptions.", "")
        self._event_rendering_helper(event, "!!!")
        self.__stream.write("\n%s\n%s\n" % (exc_name, backtrace))
        
    def handle_FatalEvent(self, event):
        backtrace = "".join(traceback.format_tb(event.exception_info[2]))
        exc_name = str(event.exception_info[0])
        exc_name = exc_name.replace("exceptions.", "")
        self._event_rendering_helper(event, "XXX")
        self.__stream.write("\n%s\n%s\n" % (exc_name, backtrace))

    def handle_UserErrorEvent(self, event):
        backtrace = "".join(traceback.format_tb(event.exception_info[2]))
        exc_name = str(event.exception_info[0])
        exc_name = exc_name.replace("exceptions.", "")
        self._user_event_rendering_helper(event, "!!!")
        self.__stream.write("\n%s\n%s\n" % (exc_name, backtrace))
        
    def handle_StdinEvent(self, event):
        self._user_event_rendering_helper(event, "<--", transport = "in")
    
    def handle_StderrEvent(self, event):
        self._user_event_rendering_helper(event, "-->", transport = "err")

    def handle_StdoutEvent(self, event):
        self._user_event_rendering_helper(event, "-->", transport = "out")

    def handle_InformationEvent(self, event):
        self._event_rendering_helper(event, "~~~")

    def handle_WarningEvent(self, event):
        self._event_rendering_helper(event, "!!!")

    def handle_StdoutPromptEvent(self, event):
        self._user_event_rendering_helper(event, "???", transport = "err")

    def handle_StderrPromptEvent(self, event):
        self._user_event_rendering_helper(event, "???", transport = "err")
    
    def handle_ShellCommandEvent(self, event):
        self._user_event_rendering_helper(event, "-->", transport = "sh")
    
    def handle_RollbackBeganEvent(self, event):
        self._event_rendering_helper(event, "!>>")
        
    def handle_RollbackFinishedEvent(self, event):
        self._event_rendering_helper(event, "!<<")
        
    def handle_TaskBeganEvent(self, event):
        self._event_rendering_helper(event, ">>>")
        
    def handle_TaskFinishedEvent(self, event):
        self._event_rendering_helper(event, "<<<")
        
    def handle_PullEvent(self, event):
        self._user_event_rendering_helper(event, "}}}")
        
    def handle_MkdirEvent(self, event):
        self._user_event_rendering_helper(event, "}}}")
    
    def _optional_output_components(self, event, icon, transport = None):

        if transport:
            optional_transport_prefix = " [%(transport)-3s]:" % dict(transport = transport)
        else:
            optional_transport_prefix = "      :"

        if event.task:

            # get the longest task name to ensure they all line up
            len_plus_brace = _get_len_of_longest_task_name()
            #task_format_string = "%%-%ss" % len_plus_brace
            task_name = event.task.name
            optional_task_context = "#%s " % (task_name)
        
        else:
            optional_task_context = ""
        
        return (optional_task_context, optional_transport_prefix)
        
    def _user_event_rendering_helper(self, event, icon, transport = None):
        
        optional_task_context, optional_transport_prefix = self._optional_output_components(event, icon, transport)
        
        self._render_header(optional_task_context, event.user.detail, event.user.fullname)

        rendered_line = "%(icon)s %(optional_transport_prefix)s %(content)s" % (dict(
            icon = icon,
            content = event.content,
            optional_transport_prefix = optional_transport_prefix
        ))
        
        self.__stream.write( rendered_line + "\n" )

    def _event_rendering_helper(self, event, icon):

        optional_task_context, dontcare = self._optional_output_components(event, icon, None)

        self._render_header(optional_task_context, None, None)

        rendered_line = "%(icon)s %(content)s" % (dict(
            icon = icon,
            content = event.content,
        ))

        self.__stream.write( rendered_line + "\n" )
        
    def _render_header(self, context, detail, fullname):
        if detail:
            detail = "(%s)" % detail
        if (self.__last_fullname != fullname or 
        self.__last_context != context or 
        self.__last_details != detail):
            header_line = "%(optional_task_context)s%(fullname)s %(detail)s" % (dict(
                optional_task_context = context,
                fullname = fullname or "",
                detail = detail or ""))
            self.__stream.write("\n" + header_line + "\n")
            
            self.__last_fullname = fullname
            self.__last_context = context 
            self.__last_details = detail

# class HtmlReceipt(object):
# 
#     def __init__(self, stream):
#         self.__stream = stream
#         self.__did_write_css = False
# 
#     def _write_css(self):
#         self.__stream.write("""
#         <style type='text/css'>
# 
#             div {
#                 margin: 2px;
#                 padding: 10px;
#             }
# 
#             div span {
#                 display: inline-block;
# 
#                 margin: auto 1px auto 1px;
#                 padding: 3px;
#                 border-width: 1px;
#                 font-size: 7px;
# 
#                 font-family: Lucida Grande, Helvetica, sans-serif;
#                 border-color: black;
#                 border-style: solid;
#                 background-color: white;
#                 color: black;
#                 opacity: 0.5;
#             }
# 
#             div span.content {
# 
#                 padding: 4px;
#                 font-size: 10px;
#             }
# 
#             div span.transport {
#                 width: 25px;
#             }
# 
#             .stdin {
#                 background-color: blue;
#             }
# 
#             .stdout {
#                 background-color: green;
#             }
# 
#             .stderr {
#                 background-color: red;
#             }
# 
# 
# 
#         </style>
#         """)
# 
#     def log_event(self, event):
# 
#         if not self.__did_write_css:
#             self._write_css()
#             self.__did_write_css = True
# 
#         render_callback = getattr(self, "_render_%s" % event.__class__.__name__)
#         if render_callback:
#             render_callback(event)
#         else:
#             raise UnknownEventError("%s does not know how to render event of type '%s'" % (self.__class__, event.__class__))
# 
#     def _render_StdinEvent(self, event):
#         self._user_event_rendering_helper(event, "stdin", "in")
# 
#     def _render_StderrEvent(self, event):
#         self._user_event_rendering_helper(event, "stderr", "err")
# 
#     def _render_StdoutEvent(self, event):
#         self._user_event_rendering_helper(event, "stdout", "out")
# 
#     def _render_InformationEvent(self, event):
#         self._user_event_rendering_helper(event, "informational")
# 
#     def _render_UserErrorEvent(self, event):
#         self._user_event_rendering_helper(event, "error")
# 
#     def _render_StdoutPromptEvent(self, event):
#         self._user_event_rendering_helper(event, "stdout_prompt", "out")
# 
#     def _render_StderrPromptEvent(self):
#         self._user_event_rendering_helper(event, "stderr_prompt", "err")
# 
#     def _user_event_rendering_helper(self, event, css_class, transport = None):
# 
#         if event.task:
#             task_context = event.task.name
#         else:
#             task_context = ""
# 
# 
#         self.__stream.write("""
#             <div class="%(css_class)s">
#                 <span>%(task_context)s</span>
#                 <span>%(fullname)s</span>
#                 <span>%(detail)s</span>
#                 <span class="transport">%(transport)s</span>
#                 <span class="content">%(content)s</span>
#             </div>
#         """ % dict(
# 
#             css_class = css_class,
#             task_context = task_context,
#             transport = transport,
#             fullname = event.user.fullname,
#             detail = event.user.detail,
#             content = event.content,
# 
#         ))
#         self.__stream.flush()
